﻿//------------------------------------------------------------------------------------------------- 
// <copyright file="ReflectionCache.cs" company="Christopher Hines">
// Copyright (c) Christopher Hines. All rights reserved.
// </copyright>
// <summary>
//    Provides static caching for reflection-based data
// </summary>
//-------------------------------------------------------------------------------------------------

namespace Copperfield
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;

    /// <summary>
    /// Provides static caching for reflection-based data
    /// </summary>
    internal static class ReflectionCache
    {
        /// <summary>
        /// Gets the context provider key
        /// </summary>
        internal const string ContextProviderKey = "ContextProviderKey";

        /// <summary>
        /// Gets the related contexts key
        /// </summary>
        internal const string RelatedContextsKey = "RelatedContextsKey";

        /// <summary>
        /// Gets the collection flattener key
        /// </summary>
        /// <value>
        /// The collection flattener key
        /// </value>
        internal const string CollectionFlattenerKey = "CollectionFlattenerKey";

        /// <summary>
        /// Gets the property info key
        /// </summary>
        internal const string PropertyInfoKey = "PropertyInfoKey";

        /// <summary>
        /// Gets the UserDefinedCache key
        /// </summary>
        private const string UserDefinedCache = "UserDefinedCache";

        /// <summary>
        /// Gets the CustomAttributesCache key
        /// </summary>
        private const string CustomAttributesCache = "CustomAttributesCache";

        /// <summary>
        /// Gets the reflection cache
        /// </summary>
        private static readonly Dictionary<object, Dictionary<string, object>> cache = new Dictionary<object, Dictionary<string, object>>();

        /// <summary>
        /// Sets the value of the specified type within the reflection dictionary
        /// </summary>
        /// <typeparam name="T">The type of object to add to the dictionary</typeparam>
        /// <param name="type">The type of object with which the item is associated</param>
        /// <param name="item">The item to add to the dictionary</param>
        /// <param name="key">The key for the new item</param>
        public static void Set<T>(Type type, T item, string key)
        {
            if (!GetUserDefinedCache(type).ContainsKey(key))
            {
                GetUserDefinedCache(type).Add(key, item);
            }
            else
            {
                GetUserDefinedCache(type)[key] = item;
            }
        }

        /// <summary>
        /// Gets the specified item from the dictionary for the specified type
        /// </summary>
        /// <typeparam name="T">The type of object to retrieve</typeparam>
        /// <param name="type">The type with which the desired object is associated</param>
        /// <param name="key">The key of the desired item</param>
        /// <returns>The requested object</returns>
        public static T Get<T>(Type type, string key)
        {
            if (!GetUserDefinedCache(type).ContainsKey(key))
            {
                return default(T);
            }

            var item = GetUserDefinedCache(type)[key];

            return item is T
                       ? (T)item
                       : default(T);
        }

        /// <summary>
        /// Attemps to get a given instance from the dictionary for the specified type
        /// </summary>
        /// <typeparam name="T">The type of object to retrieve</typeparam>
        /// <param name="type">The type with which the requested obejct is associated</param>
        /// <param name="key">The key of the requested object</param>
        /// <param name="value">The requested object</param>
        /// <returns>A value indicating whether the requested object could be found</returns>
        public static bool TryGet<T>(Type type, string key, out T value)
        {
            value = Get<T>(type, key);

            return !ReferenceEquals(value, default(T)) && !value.Equals(default(T));
        }

        /// <summary>
        /// Gets all of the attributes of a given type from a provided type
        /// </summary>
        /// <typeparam name="T">The type of attribute to retrieve</typeparam>
        /// <param name="type">The type to scan for the specified attribtue type</param>
        /// <returns>The collection of matching attributes</returns>
        [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Compiler Warning Bug Workaround")]
        public static IEnumerable<T> GetAttributesOfType<T>(Type type) where T : Attribute
        {
            return GetCustomAttributesCache(type).OfType<T>();
        }

        /// <summary>
        /// Attempts to retrieve an attribute of the specified type from the specified type
        /// </summary>
        /// <typeparam name="T">The type of attribute to retrieve</typeparam>
        /// <param name="type">The type to scan for the specified attribute type</param>
        /// <param name="attribute">The retrieved attribute</param>
        /// <returns>A value indicating whether a matching attribute could be found</returns>
        public static bool TryGetAttribute<T>(Type type, out T attribute) where T : Attribute
        {
            var attr = GetCustomAttributesCache(type).OfType<T>().FirstOrDefault();

            attribute = attr;

            return attribute != null;
        }

        /// <summary>
        /// Gets the user defined cache
        /// </summary>
        /// <param name="type">Gets the user defined cache for the specified type</param>
        /// <returns>The user defined cache for the specified type</returns>
        internal static Dictionary<string, object> GetUserDefinedCache(Type type)
        {
            if (!GetCache(type).ContainsKey(UserDefinedCache))
            {
                cache[type].Add(UserDefinedCache, new Dictionary<string, object>());
            }

            return cache[type][UserDefinedCache] as Dictionary<string, object>;
        }

        /// <summary>
        /// Gets the custom attribute cache for the specified type
        /// </summary>
        /// <param name="type">The type for which the cache should be returned</param>
        /// <returns>The cache for the specified type</returns>
        internal static IEnumerable<object> GetCustomAttributesCache(Type type)
        {
            if (!GetCache(type).ContainsKey(CustomAttributesCache))
            {
                cache[type].Add(CustomAttributesCache, type.GetCustomAttributes(false));
            }

            return GetCache(type)[CustomAttributesCache] as object[];
        }

        /// <summary>
        /// Gets the cache for the specified object
        /// </summary>
        /// <param name="obj">The object for which the cache should be returned</param>
        /// <returns>The cache for the specified object</returns>
        internal static Dictionary<string, object> GetCache(object obj)
        {
            if (!cache.ContainsKey(obj))
            {
                cache.Add(obj, new Dictionary<string, object>());
            }

            return cache[obj];
        }
    }
}
